#pragma once

#include "uartExcept.hpp"
#include <map>
#include <string>
#include <termios.h>

class UartParam {
private:
    std::string _port;
    int _baudrate;
    int _databits;
    int _stopbits;
    char _parity;

public:
    UartParam()
        : _baudrate(19200), _databits(8), _stopbits(1), _parity('E'){};

    UartParam(const UartParam &) = default;
    UartParam(UartParam &&) = default;

    UartParam &operator=(const UartParam &) = default;
    UartParam &operator=(UartParam &&) = default;

    UartParam(const std::string &port)
        : _port(port), _baudrate(19200), _databits(8), _stopbits(1), _parity('E'){};

    UartParam(const std::string &port, int baudrate, int databits, int stopbits,
              const std::string &parity)
        : _port(port) {
        _setAndCheckBaudrate(baudrate);
        _setAndCheckDatabits(databits);
        _setAndCheckStopbits(stopbits);
        _setAndCheckParity(parity);
    };

    UartParam(const std::string &port, int baudrate, int databits, int stopbits,
              char parity)
        : _port(port) {
        _setAndCheckBaudrate(baudrate);
        _setAndCheckDatabits(databits);
        _setAndCheckStopbits(stopbits);
        _setAndCheckParity(parity);
    };

    bool operator==(const UartParam &u) const {
        return _port == u._port && _baudrate == u._baudrate &&
               _databits == u._databits && _stopbits == u._stopbits &&
               _parity == u._parity;
    }

private:
    static inline std::map<int, int> baudrateConfigMap = {
        {110, B110},
        {300, B300},
        {600, B600},
        {1200, B1200},
        {2400, B2400},
        {4800, B4800},
        {9600, B9600},
        {19200, B19200},
        {38400, B38400},
#ifdef B57600
        {57600, B57600},
#endif
#ifdef B115200
        {115200, B115200},
#endif
#ifdef B230400
        {230400, B230400},
#endif
#ifdef B460800
        {460800, B460800},
#endif
#ifdef B500000
        {500000, B500000},
#endif
#ifdef B576000
        {576000, B576000},
#endif
#ifdef B921600
        {921600, B921600},
#endif
#ifdef B1000000
        {1000000, B1000000},
#endif
#ifdef B1152000
        {1152000, B1152000},
#endif
#ifdef B1500000
        {1500000, B1500000},
#endif
#ifdef B2500000
        {2500000, B2500000},
#endif
#ifdef B3000000
        {3000000, B3000000},
#endif
#ifdef B3500000
        {3500000, B3500000},
#endif
#ifdef B4000000
        {4000000, B4000000},
#endif
    };

    void _setAndCheckBaudrate(int b) {
        if (9600 == B9600) {
            _baudrate = b;
            return;
        }

        if (baudrateConfigMap.find(b) == baudrateConfigMap.end()) {
            throw UartParamError("bad baudrate, Unsupported baud rates : " +
                                 std::to_string(b));
            return;
        }

        _baudrate = baudrateConfigMap[b];
    }

    void _setAndCheckStopbits(int s) {
        if (s != 1 && s != 2) {
            throw UartParamError("bad stopbits, Unsupported stop bit : " +
                                 std::to_string(s));
            return;
        }

        _stopbits = s;
    }

    void _setAndCheckDatabits(int d) {
        if (d != 5 && d != 6 && d != 7 && d != 8) {
            throw UartParamError("bad databits, Unsupported data bits : " +
                                 std::to_string(d));
            return;
        }

        _databits = d;
    }

    void _setAndCheckParity(char p) {
        auto c = std::toupper(p);
        if (c != 'N' && c != 'E' && c != 'O') {
            throw UartParamError("bad parity, Unsupported parity bit : " +
                                 std::string{p});
            return;
        }

        _parity = c;
    }

    void _setAndCheckParity(const std::string &p) {
        /* can be 'none & NONE & None' || 'Even & even' || 'ODD & odd & Odd' */
        if (!p.length()) {
            throw UartParamError("bad parity, Unsupported parity bit");
            return;
        }

        auto c = std::toupper(p.at(0));

        if (c != 'N' && c != 'E' && c != 'O') {
            throw UartParamError("bad parity, Unsupported parity bit : " + p);
            return;
        }

        _parity = c;
    }

public:
    const char *port() const { return _port.c_str(); }
    int baudrate() const { return _baudrate; }

    int rawBaudrate(void) const {
        for (const auto &[key, val] : baudrateConfigMap) {
            if (val == _baudrate) {
                return key;
            }
        };
        return 0;
    }

    int stopbits() const { return _stopbits; }
    int databits() const { return _databits; }
    char parity() const { return _parity; }

    UartParam &setPort(const std::string &port) {
        _port = port;
        return *this;
    }

    UartParam &setBaudrate(int baudrate) {
        _setAndCheckBaudrate(baudrate);
        return *this;
    }

    UartParam &setDatabits(int databits) {
        _setAndCheckDatabits(databits);
        return *this;
    }

    UartParam &setStopbits(int stopbits) {
        _setAndCheckStopbits(stopbits);
        return *this;
    }

    UartParam &setParity(char parity) {
        _setAndCheckParity(parity);
        return *this;
    }

    UartParam &setParity(const std::string &parity) {
        _setAndCheckParity(parity);
        return *this;
    }
};
